package com.chenjinyan.zuul.filter;

import com.chenjinyan.zuul.constant.ExceptionEnum;
import com.chenjinyan.zuul.exception.DescribeException;
import com.chenjinyan.zuul.service.impl.GrantedAuthorityImpl;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

/**
 * token的校验
 * 该类继承自BasicAuthenticationFilter，在doFilterInternal方法中，
 * 从http头的Authorization 项读取token数据，然后用Jwts包提供的方法校验token的合法性。
 * 如果校验通过，就认为这是一个取得授权的合法请求
 */
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {


    /**
     * 签名key
     */
    public static final String SIGNING_KEY = "spring-security-@Jwt!&Secret^#";

    private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("token");

        if (header == null) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);

    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        {
            long start = System.currentTimeMillis();
            String token = request.getHeader("token");
            if (token == null || token.isEmpty()) {
                throw new DescribeException(ExceptionEnum.TOKEN_IS_NULL);
            }
            // parse the token.
            String user = null;
            try {
                user = Jwts.parser()
                        .setSigningKey(SIGNING_KEY)
                        .parseClaimsJws(token)
                        .getBody()
                        .getSubject();
                long end = System.currentTimeMillis();
                logger.info("执行时间: {}", (end - start) + " 毫秒");
                if (user != null) {
                    String[] split = user.split("-")[1].split(",");
                    ArrayList<GrantedAuthority> authorities = new ArrayList<>();
                    for (int i = 0; i < split.length; i++) {
                        authorities.add(new GrantedAuthorityImpl(split[i]));
                    }
                    return new UsernamePasswordAuthenticationToken(user, null, authorities);
                }

            } catch (ExpiredJwtException e) {
                logger.error("Token已过期: {} " + e);
                throw new DescribeException(ExceptionEnum.TOKEN_IS_NULL);
            } catch (UnsupportedJwtException e) {
                logger.error("Token格式错误: {} " + e);
                throw new DescribeException(ExceptionEnum.TOKEN_IS_NULL);
            } catch (MalformedJwtException e) {
                logger.error("Token没有被正确构造: {} " + e);
                throw new DescribeException(ExceptionEnum.TOKEN_IS_NULL);
            } catch (SignatureException e) {
                logger.error("签名失败: {} " + e);
                throw new DescribeException(ExceptionEnum.TOKEN_IS_NULL);
            } catch (IllegalArgumentException e) {
                logger.error("非法参数异常: {} " + e);
                throw new DescribeException(ExceptionEnum.TOKEN_IS_NULL);
            }

            return null;
        }

    }
}

